import threading
import time
import redis
from dotenv import load_dotenv
import os
from typing import Dict, List  # 类型提示，提升代码可读性（学习重点）

# -------------------------- 学习重点：类型提示（Python 3.5+支持，让代码更易读） --------------------------
# 定义数据类型别名（后续代码可直接用这些别名，避免重复写复杂类型）
PositionDict = Dict[str, int]  # 持仓字典：{合约代码: 持仓量}，如{"JD11": -5, "AP01": 4}
AccountPosition = Dict[str, any]  # 账户持仓：{"account": "100000012", "lots": PositionDict}
Order指令 = Dict[str, any]  # 下单指令：{"account": "xxx", "contract": "xxx", "direction": "BUY", "volume": 1, "price": 1000}

# -------------------------- 配置加载（同模块1） --------------------------
load_dotenv()
REDIS_CONFIG = {
    "host": os.getenv("REDIS_HOST", "127.0.0.1"),
    "port": int(os.getenv("REDIS_PORT", 6379)),
    "password": os.getenv("REDIS_PWD", ""),
    "db": 0
}

# -------------------------- 学习重点：模拟实盘持仓接口（真实环境需替换为CTP查持仓） --------------------------
def get_real_position(account: str) -> PositionDict:
    """
    获取实盘账户的当前持仓（模拟函数，真实环境需调用CTP的查持仓接口）
    :param account: 账户号
    :return: 实盘持仓字典（格式同目标持仓）
    """
    # 真实逻辑：调用CTP的CThostFtdcTraderApi查询持仓，返回多空持仓合并后的数据
    # 这里用模拟数据（假设实盘持仓与目标持仓有差异，方便测试匹配逻辑）
    mock_real_positions = {
        "100000012": {"JD11": -3, "AP01": 4, "AG12": 0},  # 目标JD11是-5，实盘是-3（差-2，需再空2手）
        "100000013": {"JD11": 0, "SM01": -2, "MA01": -10}  # 目标JD11是-2，实盘是0（差-2，需空2手）
    }
    return mock_real_positions.get(account, {})

# -------------------------- 学习重点：持仓对比与下单指令生成逻辑 --------------------------
def calculate_order_instructions(target_pos: PositionDict, real_pos: PositionDict) -> List[Order指令]:
    """
    对比目标持仓和实盘持仓，生成需要的下单指令（多退少补）
    持仓量规则：正数=多头，负数=空头，0=无持仓
    :param target_pos: 目标持仓（来自Redis）
    :param real_pos: 实盘持仓（来自CTP）
    :return: 下单指令列表（空列表表示持仓已匹配）
    """
    order_list = []
    
    # 1. 合并所有需要处理的合约（目标持仓+实盘持仓，避免遗漏）
    all_contracts = set(target_pos.keys()).union(set(real_pos.keys()))
    
    for contract in all_contracts:
        # 目标持仓量（无则为0）
        target_volume = target_pos.get(contract, 0)
        # 实盘持仓量（无则为0）
        real_volume = real_pos.get(contract, 0)
        # 持仓差值（目标 - 实盘，正数=需加多，负数=需加空，0=无需操作）
        diff_volume = target_volume - real_volume
        
        if diff_volume == 0:
            continue  # 持仓匹配，跳过
        
        # 2. 生成下单指令（根据差值判断多空方向）
        if diff_volume > 0:
            # 差值为正：需开多头（或平空头后开多）
            # 逻辑：先平空头（若实盘有空头），再开多头（若还有剩余差值）
            if real_volume < 0:
                # 实盘有空头，先平空（平空=买入平仓，方向为BUY_CLOSE）
                close_volume = min(abs(real_volume), diff_volume)
                order_list.append({
                    "contract": contract,
                    "direction": "BUY_CLOSE",  # 买入平仓（平空头）
                    "volume": close_volume,
                    "remark": f"实盘空头{real_volume}，目标{target_volume}，需平空{close_volume}"
                })
                # 平空后，剩余差值 = 原差值 - 平空量
                diff_volume -= close_volume
                if diff_volume == 0:
                    continue
            # 剩余差值为正，开多头（方向为BUY_OPEN）
            order_list.append({
                "contract": contract,
                "direction": "BUY_OPEN",  # 买入开仓（开多头）
                "volume": diff_volume,
                "remark": f"实盘多头不足，需开多{diff_volume}手"
            })
        
        else:  # diff_volume < 0（差值为负，需开空头或平多头）
            # 逻辑：先平多头（若实盘有多头），再开空头（若还有剩余差值）
            if real_volume > 0:
                # 实盘有多头，先平多（平多=卖出平仓，方向为SELL_CLOSE）
                close_volume = min(real_volume, abs(diff_volume))
                order_list.append({
                    "contract": contract,
                    "direction": "SELL_CLOSE",  # 卖出平仓（平多头）
                    "volume": close_volume,
                    "remark": f"实盘多头{real_volume}，目标{target_volume}，需平多{close_volume}"
                })
                # 平多后，剩余差值（绝对值）= 原差值绝对值 - 平多量
                diff_volume = -(abs(diff_volume) - close_volume)
                if diff_volume == 0:
                    continue
            # 剩余差值为负，开空头（方向为SELL_OPEN）
            order_list.append({
                "contract": contract,
                "direction": "SELL_OPEN",  # 卖出开仓（开空头）
                "volume": abs(diff_volume),
                "remark": f"实盘空头不足，需开空{abs(diff_volume)}手"
            })
    
    return order_list

# -------------------------- 学习重点：多线程处理单个账户（每个账户一个线程） --------------------------
class AccountPositionMatcher(threading.Thread):
    """
    单个账户的持仓匹配线程（继承threading.Thread，实现多线程）
    功能：获取目标持仓→获取实盘持仓→生成下单指令→推送到Redis订单队列
    """
    def __init__(self, account: str, redis_client: redis.Redis):
        super().__init__(name=f"Matcher-{account}")  # 线程名，方便调试
        self.account = account  # 负责的账户号
        self.redis_client = redis_client  # Redis连接对象（复用，避免重复创建）
        self.running = True  # 线程运行开关（用于优雅停止）
    
    def run(self):
        """线程核心运行逻辑（start()方法会自动调用）"""
        print(f"持仓匹配线程启动 → 账户：{self.account}，线程名：{self.name}")
        
        while self.running:
            try:
                # 1. 从Redis获取目标持仓（Key：position:{account}，同模块1）
                redis_key = f"position:{self.account}"
                if not self.redis_client.exists(redis_key):
                    # 无目标持仓数据，间隔5秒重试
                    time.sleep(5)
                    continue
                
                # 2. 读取并解析目标持仓（同模块1的解析逻辑）
                lots_str = self.redis_client.hget(redis_key, "lots").decode("utf-8")
                target_pos = eval(lots_str)  # 简化处理，真实环境用ast.literal_eval
                print(f"获取目标持仓 → 账户：{self.account}，持仓：{target_pos}")
                
                # 3. 获取实盘持仓（调用模拟/真实CTP接口）
                real_pos = get_real_position(self.account)
                print(f"获取实盘持仓 → 账户：{self.account}，持仓：{real_pos}")
                
                # 4. 计算下单指令
                order_list = calculate_order_instructions(target_pos, real_pos)
                
                if not order_list:
                    print(f"持仓已匹配 → 账户：{self.account}，无需下单")
                    # 删除Redis目标持仓Key，避免重复处理
                    self.redis_client.delete(redis_key)
                    time.sleep(10)
                    continue
                
                # 5. 将下单指令推送到Redis订单队列（供后续发单模块使用）
                # Redis队列Key：order_queue:{account}（列表类型，用lpush添加到队首）
                order_queue_key = f"order_queue:{self.account}"
                for order in order_list:
                    # 给订单添加账户信息（发单模块需要知道哪个账户下单）
                    order_with_account = {**order, "account": self.account}
                    # 转成字符串存入Redis队列（真实环境用JSON）
                    self.redis_client.lpush(order_queue_key, str(order_with_account))
                    print(f"推送下单指令 → 账户：{self.account}，指令：{order_with_account}")
                
                # 6. 处理完成，删除目标持仓Key
                self.redis_client.delete(redis_key)
                print(f"账户{self.account}持仓匹配完成，等待下一次数据...")
                
            except Exception as e:
                print(f"账户{self.account}持仓匹配出错 → 错误：{str(e)}")
            
            # 间隔10秒再次检查持仓（可根据需求调整频率）
            time.sleep(10)
    
    def stop(self):
        """优雅停止线程（外部调用）"""
        self.running = False
        print(f"持仓匹配线程停止 → 账户：{self.account}")

# -------------------------- 主函数：启动多账户持仓匹配 --------------------------
def main_start_matchers(target_accounts: List[str]):
    """
    启动多个账户的持仓匹配线程（多线程）
    :param target_accounts: 需要处理的账户列表
    """
    # 1. 连接Redis
    try:
        redis_client = redis.Redis(**REDIS_CONFIG)
        redis_client.ping()
        print("Redis连接成功，准备启动持仓匹配线程...")
    except Exception as e:
        print(f"Redis连接失败 → 错误：{str(e)}")
        return
    
    # 2. 创建并启动每个账户的线程
    matcher_threads = []
    for account in target_accounts:
        matcher = AccountPositionMatcher(account, redis_client)
        matcher.start()  # 启动线程（自动调用run()方法）
        matcher_threads.append(matcher)
        time.sleep(1)  # 间隔1秒启动，避免同时连接CTP/Redis造成压力
    
    # 3. 主线程等待（避免程序退出，实际部署需用守护进程）
    try:
        while True:
            time.sleep(3600)  # 主线程休眠1小时，长期运行
    except KeyboardInterrupt:
        # 捕获Ctrl+C，停止所有线程
        print("\n收到停止信号，正在停止所有持仓匹配线程...")
        for matcher in matcher_threads:
            matcher.stop()
            matcher.join()  # 等待线程结束
        print("所有线程已停止")
    
    # 4. 关闭Redis连接
    redis_client.close()

if __name__ == "__main__":
    # 需要处理的账户列表（根据子服务器分配修改）
    TARGET_ACCOUNTS = ["100000012", "100000013"]
    main_start_matchers(TARGET_ACCOUNTS)